#!/bin/bash
# https://chrony-project.org/releases/chrony-4.5.tar.gz

set -euo pipefail

#****** vars ******#
CHRONY_VERSION="4.5"
CHRONY_INSTALL_PATH="/usr/local"
CHRONY_USER="chrony"
CHRONY_TIME_SERVER=("ntp.aliyun.com" "ntp.tencent.com" "time.windows.com")
CHRONY_SERVER_FLAG=false # true or false
CHRONY_SRC_DIR="/usr/local/src"
CHRONY_URL="https://chrony-project.org/releases"

#****** functions ******#
color() {
    local message="$1"
    local status="$2"
    local terminal_width
    terminal_width=$(tput cols)
    local middle_column=$(( terminal_width / 2))
    local SETCOLOR_SUCCESS="\033[1;32m"  # 绿色
    local SETCOLOR_FAILURE="\033[1;31m"  # 红色
    local SETCOLOR_WARNING="\033[1;33m"  # 黄色
    local SETCOLOR_NORMAL="\033[0m"      # 重置颜色
    printf '%s\n\033[%sG' "${message}"  "${middle_column}"
    # Print the status message
    printf "["
    case ${status} in
        success|0)
            echo -en  "${SETCOLOR_SUCCESS}  OK  "
            ;;
        failure|1)
            echo -en  "${SETCOLOR_FAILURE}FAILED"
            ;;
        *)
            echo -en "${SETCOLOR_WARNING}WARNING"
            ;;
    esac
    echo -e "${SETCOLOR_NORMAL}]"
}

create_user () {
    if ! id "$1" &> /dev/null;then
        useradd -r -M -s /sbin/nologin "$1" \
        && color "$1 user is created." 0 \
        || { color "$1 user create failed.";exit; }
    else
        color "$1 user is existed." 2
    fi
}

judge_current_user () {
    if [ "$(id -u)" = "0" ];then
        color "current user is administrator" 0
    else
        color "current user is not administrator" 1
        exit
    fi
}

configfile_add_config () { # $1 is needed to add config,$2 is config file path
    if grep -q "$1" "$2";then
        color "$2 is already have $1" 0
    else
        echo "$1" >> "$2"
        color "$2 add config:  $1" 0 
    fi
}

judge_true_false () { # $1 is var, $2 is function
    if ! [[ $1 =~ ^true|false$ ]];then
        color "$1 value is not true or false,check your vars set." 1
        exit
    else
        $2
    fi
}

download_file () { # $1参数为下载url $2参数为下载目标路径
    wget "$1" -P "$2" &> /dev/null
}

prepare_chrony_src_file () {
    if [ -e ${CHRONY_SRC_DIR}/chrony-${CHRONY_VERSION}.tar.gz ];then
        CHRONY_SRC_FILE_PATH="${CHRONY_SRC_DIR}/chrony-${CHRONY_VERSION}.tar.gz"
    elif [ -e chrony-${CHRONY_VERSION}.tar.gz ];then
        CHRONY_SRC_FILE_PATH="$(pwd)/chrony-${CHRONY_VERSION}.tar.gz"
    else
        color "src dir and current dir don't find chrony-${CHRONY_VERSION}.tar.gz,try download it ..." 2
        wget "${CHRONY_URL}/chrony-${CHRONY_VERSION}.tar.gz" -P "${CHRONY_SRC_DIR}" || { color "download fail." 1;exit; }
        CHRONY_SRC_FILE_PATH="${CHRONY_SRC_DIR}/chrony-${CHRONY_VERSION}.tar.gz"
    fi
    tar -xvf "${CHRONY_SRC_FILE_PATH}" -C "${CHRONY_SRC_DIR}"
    color "chrony src file prepare finsh." 0
}

compile_chrony_src_file () {
    color "apt install chrony compile needed dependency." 2
    apt update
    apt install -y gcc make libcap-dev
    cd "${CHRONY_SRC_DIR}/chrony-${CHRONY_VERSION}" || { color "no such dir." 1;exit; }
    ./configure --prefix=${CHRONY_INSTALL_PATH}/chrony --with-user=${CHRONY_USER} --mandir=/usr/share/man
    make -j "$(nproc)"
    make install
    cd -  || { color "no such dir." 1;exit; }
    color "chrony compile finsh." 0
}

install_chrony () {
    cat > ${CHRONY_INSTALL_PATH}/chrony/chrony.conf <<__EOF__
# Use public NTP servers from the pool.ntp.org project.
#pool pool.ntp.org iburst
#server ntp.xxx.com  iburst
# Record the rate at which the system clock gains/losses time.
driftfile ${CHRONY_INSTALL_PATH}/chrony/drift

# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3

# Enable kernel synchronization of the real-time clock (RTC).
rtcsync
__EOF__

    for((i=0;i<${#CHRONY_TIME_SERVER[@]};i++));do
        configfile_add_config "server ${CHRONY_TIME_SERVER[i]}  iburst"  "${CHRONY_INSTALL_PATH}/chrony/chrony.conf"
    done

    if [ "$CHRONY_SERVER_FLAG" = "true" ];then
        configfile_add_config "allow 0.0.0.0/0" "${CHRONY_INSTALL_PATH}/chrony/chrony.conf"
        configfile_add_config "local stratum 10" "${CHRONY_INSTALL_PATH}/chrony/chrony.conf"
    fi

    cat > /lib/systemd/system/chronyd.service <<__EOF__
[Unit]
Description=NTP client/server
Documentation=man:chronyd(8) man:chrony.conf(5)
After=ntpdate.service sntp.service ntpd.service
Conflicts=ntpd.service systemd-timesyncd.service
ConditionCapability=CAP_SYS_TIME

[Service]
Type=forking
#EnvironmentFile=-/etc/sysconfig/chronyd
ExecStart=${CHRONY_INSTALL_PATH}/chrony/sbin/chronyd -u ${CHRONY_USER} -f ${CHRONY_INSTALL_PATH}/chrony/chrony.conf

CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE
CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE
CapabilityBoundingSet=~CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_MKNOD CAP_SYS_ADMIN
CapabilityBoundingSet=~CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_PACCT
CapabilityBoundingSet=~CAP_SYS_PTRACE CAP_SYS_RAWIO CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM
DeviceAllow=char-pps rw
DeviceAllow=char-ptp rw
DeviceAllow=char-rtc rw
DevicePolicy=closed
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
PrivateTmp=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectProc=invisible
ProtectSystem=strict
ReadWritePaths=${CHRONY_INSTALL_PATH}/chrony
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallFilter=~@cpu-emulation @debug @module @mount @obsolete @raw-io @reboot @swap

# Adjust restrictions for /usr/sbin/sendmail (mailonchange directive)
NoNewPrivileges=no
ReadWritePaths=-/var/spool
RestrictAddressFamilies=AF_NETLINK

[Install]
WantedBy=multi-user.target
__EOF__

    chown -R ${CHRONY_USER}:${CHRONY_USER} ${CHRONY_INSTALL_PATH}/chrony
    systemctl daemon-reload
    systemctl enable chronyd.service
    systemctl start chronyd.service \
    && { color "chronyd.service start sucess!" 0; echo -e "check time sync status by use \E[33;1m${CHRONY_INSTALL_PATH}/chrony/bin/chronyc sources -v\E[0m"; } \
    || { color "chronyd.service  start fail." 1; exit; }
}



main () {
    judge_current_user
    judge_true_false $CHRONY_SERVER_FLAG prepare_chrony_src_file
    create_user ${CHRONY_USER}
    compile_chrony_src_file
    install_chrony
}

main

